package com.kexilo.core.common.aspect;

import com.kexilo.core.common.annotation.Debounce;
import com.kexilo.core.common.exception.ServiceException;
import com.kexilo.core.common.utils.IpUtils;
import com.kexilo.core.common.utils.RedisUtils;
import com.kexilo.core.common.utils.SecurityUtils;
import com.kexilo.core.common.utils.ServletUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

/**
 * 防抖切面处理器
 * 
 * @author Kexilo
 */
@Aspect
@Component
public class DebounceAspect {
    
    private static final Logger log = LoggerFactory.getLogger(DebounceAspect.class);
    
    @Autowired
    private RedisUtils redisUtils;
    
    private final ExpressionParser parser = new SpelExpressionParser();
    
    @Around("@annotation(debounce)")
    public Object around(ProceedingJoinPoint joinPoint, Debounce debounce) throws Throwable {
        String debounceKey = generateDebounceKey(joinPoint, debounce);
        
        // 检查是否在防抖时间窗口内
        if (redisUtils.hasKey(debounceKey)) {
            log.warn("防抖检查失败，请求过于频繁: key={}", debounceKey);
            throw new ServiceException(debounce.message());
        }
        
        // 设置防抖标识
        long intervalInSeconds = debounce.timeUnit().toSeconds(debounce.interval());
        redisUtils.set(debounceKey, System.currentTimeMillis(), intervalInSeconds);
        
        try {
            // 执行方法
            Object result = joinPoint.proceed();
            
            // 如果设置了操作完成后删除key，则删除防抖标识
            if (debounce.deleteAfterCompletion()) {
                redisUtils.del(debounceKey);
                log.debug("防抖操作完成，删除防抖标识: key={}", debounceKey);
            } else {
                log.debug("防抖操作完成，保留防抖标识: key={}", debounceKey);
            }
            
            return result;
        } catch (Exception e) {
            // 如果执行失败，根据配置决定是否删除防抖标识
            if (debounce.deleteAfterCompletion()) {
                redisUtils.del(debounceKey);
                log.warn("方法执行失败，删除防抖标识: key={}", debounceKey);
            }
            throw e;
        }
    }
    
    /**
     * 生成防抖key
     */
    private String generateDebounceKey(ProceedingJoinPoint joinPoint, Debounce debounce) {
        StringBuilder keyBuilder = new StringBuilder(debounce.value());
        
        String customKey = debounce.key();
        if (StringUtils.hasText(customKey)) {
            // 使用SpEL表达式解析自定义key
            String parsedKey = parseSpelExpression(joinPoint, customKey);
            keyBuilder.append(parsedKey);
        } else {
            // 根据策略生成key
            switch (debounce.strategy()) {
                case USER_METHOD:
                    keyBuilder.append(getUserMethodKey(joinPoint));
                    break;
                case IP_METHOD:
                    keyBuilder.append(getIpMethodKey(joinPoint));
                    break;
                case REQUEST_PARAMS:
                    keyBuilder.append(getRequestParamsKey(joinPoint));
                    break;
                case GLOBAL_METHOD:
                    keyBuilder.append(getGlobalMethodKey(joinPoint));
                    break;
                case CUSTOM_KEY:
                    // 自定义key已在上面处理
                    break;
                default:
                    keyBuilder.append(getUserMethodKey(joinPoint));
            }
        }
        
        return keyBuilder.toString();
    }
    
    /**
     * 解析SpEL表达式
     */
    private String parseSpelExpression(ProceedingJoinPoint joinPoint, String expression) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Object[] args = joinPoint.getArgs();
            Parameter[] parameters = method.getParameters();
            
            EvaluationContext context = new StandardEvaluationContext();
            
            // 设置方法参数
            for (int i = 0; i < parameters.length; i++) {
                context.setVariable(parameters[i].getName(), args[i]);
            }
            
            // 设置常用变量
            try {
                context.setVariable("userId", SecurityUtils.getUserId());
                context.setVariable("username", SecurityUtils.getUsername());
            } catch (Exception e) {
                log.debug("获取用户信息失败，使用默认值");
                context.setVariable("userId", "anonymous");
                context.setVariable("username", "anonymous");
            }
            
            context.setVariable("ip", IpUtils.getIpAddr(ServletUtils.getRequest()));
            context.setVariable("methodName", method.getName());
            context.setVariable("className", method.getDeclaringClass().getSimpleName());
            
            Expression exp = parser.parseExpression(expression);
            Object value = exp.getValue(context);
            return value != null ? value.toString() : "";
        } catch (Exception e) {
            log.error("解析SpEL表达式失败: {}", expression, e);
            return expression;
        }
    }
    
    /**
     * 获取基于用户和方法的key
     */
    private String getUserMethodKey(ProceedingJoinPoint joinPoint) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            String userId = SecurityUtils.getUserId().toString();
            return String.format("user:%s:method:%s.%s", 
                    userId, 
                    method.getDeclaringClass().getSimpleName(), 
                    method.getName());
        } catch (Exception e) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
            return String.format("ip:%s:method:%s.%s", 
                    ip, 
                    method.getDeclaringClass().getSimpleName(), 
                    method.getName());
        }
    }
    
    /**
     * 获取基于IP和方法的key
     */
    private String getIpMethodKey(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        return String.format("ip:%s:method:%s.%s", 
                ip, 
                method.getDeclaringClass().getSimpleName(), 
                method.getName());
    }
    
    /**
     * 获取基于请求参数的key
     */
    private String getRequestParamsKey(ProceedingJoinPoint joinPoint) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Object[] args = joinPoint.getArgs();
            
            StringBuilder sb = new StringBuilder();
            
            // 添加用户信息
            try {
                sb.append("user:").append(SecurityUtils.getUserId()).append(":");
            } catch (Exception e) {
                sb.append("ip:").append(IpUtils.getIpAddr(ServletUtils.getRequest())).append(":");
            }
            
            // 添加方法信息
            sb.append("method:").append(method.getDeclaringClass().getSimpleName())
              .append(".").append(method.getName());
            
            // 添加参数hash
            if (args != null && args.length > 0) {
                sb.append(":params:");
                StringBuilder paramsSb = new StringBuilder();
                for (Object arg : args) {
                    if (arg != null) {
                        paramsSb.append(arg.toString()).append(",");
                    }
                }
                sb.append(md5(paramsSb.toString()));
            }
            
            return sb.toString();
        } catch (Exception e) {
            log.error("生成请求参数key失败", e);
            return getUserMethodKey(joinPoint);
        }
    }
    
    /**
     * 获取全局方法key
     */
    private String getGlobalMethodKey(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        return String.format("global:method:%s.%s", 
                method.getDeclaringClass().getSimpleName(), 
                method.getName());
    }
    
    /**
     * MD5加密
     */
    private String md5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            return String.valueOf(input.hashCode());
        }
    }
}
